package com.lagou.binding;

import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.pojo.User;
import com.lagou.relection.ParamNameResolver;
import com.lagou.relection.TypeParameterResolver;
import com.lagou.sqlSession.SqlSession;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Mapper 方法。在 Mapper 接口中，每个定义的方法，对应一个 MapperMethod 对象
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 * @author Kazuki Shimizu
 */
public class MapperMethod {

    /**
     * SqlCommand 对象
     */
    private final SqlCommand command;
    /**
     * MethodSignature 对象
     */
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判断mapper中的方法类型，最终调用的还是SqlSession中的方法
        switch (command.getType()) {
            case "update": {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = sqlSession.update(command.getName(), param);
                break;
            }
            case "select":
                // 执行查询，返回列表
                if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // 执行查询，返回单个对象
                } else {
                    // 转换参数
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 查询单条
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            default:
                throw new RuntimeException("Unknown execution method for: " + command.getName());
        }
        return result;
    }

    private Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<User> result = null;
        // 转换参数
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行 SELECT 操作
        result = sqlSession.selectList(command.getName(), param);
        // 直接返回的结果
        return result; // 情况三，默认
    }

    /**
     * SQL 命令
     */
    public static class SqlCommand {

        /**
         * {@link MappedStatement#getId()}
         */
        private final String name;
        /**
         * SQL 命令类型
         */
        private final String type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            final String methodName = method.getName();
            final Class<?> declaringClass = method.getDeclaringClass();
            // 获得 MappedStatement 对象
            MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                    configuration);
            // 找不到 MappedStatement
            if (ms == null) {
                    throw new RuntimeException("Invalid bound statement (not found): "
                            + mapperInterface.getName() + "." + methodName);
            // 找到 MappedStatement
            } else {
                // 获得 name
                name = ms.getId();
                // 获得 type
                type = ms.getCommandType();
            }
        }

        public String getName() {
            return name;
        }

        public String getType() {
            return type;
        }

        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
                                                       Class<?> declaringClass, Configuration configuration) {
            // 获得编号
            String statementId = mapperInterface.getName() + "." + methodName;
            // 如果有，获得 MappedStatement 对象，并返回
            return configuration.getMappedStatement(statementId);
        }

    }

    /**
     * 方法签名
     */
    public static class MethodSignature {

        /**
         * 返回类型是否为集合
         */
        private final boolean returnsMany;
        /**
         * 返回类型
         */
        private final Class<?> returnType;
        private final ParamNameResolver paramNameResolver;

        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
            // 初始化 returnType 属性
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
                this.returnType = (Class<?>) resolvedReturnType;
            // 初始化 returnsMany 属性
            this.returnsMany = Collection.class.isAssignableFrom(this.returnType) || this.returnType.isArray();
            this.paramNameResolver = new ParamNameResolver(configuration, method);
        }

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return paramNameResolver.getNamedParams(args);
        }

        public Class<?> getReturnType() {
            return returnType;
        }

        public boolean returnsMany() {
            return returnsMany;
        }

        /**
         * 获得指定参数类型在方法参数中的位置
         *
         * @param method 指定方法
         * @param paramType 参数类型
         * @return 位置
         */
        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
            Integer index = null;
            // 遍历方法参数
            final Class<?>[] argTypes = method.getParameterTypes();
            for (int i = 0; i < argTypes.length; i++) {
                if (paramType.isAssignableFrom(argTypes[i])) { // 类型符合
                    // 获得第一次的位置
                    if (index == null) {
                        index = i;
                    // 如果重复类型了，则抛出 RuntimeException 异常
                    } else {
                        throw new RuntimeException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
                    }
                }
            }
            return index;
        }

    }

}
